
#include <BFile>
#include <BLabel>
#include <BHBoxLayout>
#include <BVBoxLayout>
#include <BXmlDocument>
#include <BScrollBar>
#include <BKeyEvent>
#include <BCharEvent>
#include <BMouseEvent>
#include <BStyle>
#include <BSystem>

#include <BHtmlWidget>
#include "member_BHtmlWidget.h"
#include "member_BHtmlItem.h"
#include "member_BXmlNode.h"

using namespace BWE;

#define member					(*(member_BHtmlWidget*)_ptr)
#define member_allocate()		_ptr = new member_BHtmlWidget(this)
#define member_release()		delete (member_BHtmlWidget*)_ptr

BHtmlWidget::BHtmlWidget(BWidget* parent) : BScrollArea(parent)
{
	member_allocate();
}
BHtmlWidget::~BHtmlWidget()
{
	member_release();
}

bool BHtmlWidget::load(const BString& fileName)
{
	BFile file(fileName);
	if (!file.open(IO_Read))
		return false;

	BXmlDocument doc;
	BXmlNode* root = doc.read(file);
	if (!root)
		return false;

	BXmlNode* body = root->child("body");
	if (!body)
		return false;

	member.rootNode = root;
	member.bodyItem = new BHtmlItem(body);
	
	root->removeChild(body);
	root->addChild(member.bodyItem);

	member.freshStyle(member.bodyItem);
	member.dirty = true;

	return true;
}
bool BHtmlWidget::save(const BString& fileName) const
{
	if (!member.bodyItem)
		return false;

	BFile file(fileName);
	if (!file.open(IO_Write))
		return false;

	BXmlNode* root = member.bodyItem->root();

	BXmlDocument document;
	bool ret = document.write(file, root);
	if (!ret)
		return false;

	return true;
}

bool BHtmlWidget::read(const BStream& stream)
{
	BXmlDocument doc;
	BXmlNode* root = doc.read(stream);
	if (!root)
		return false;

	BXmlNode* body = root->child("body");
	if (!body)
		return false;

	member.rootNode = root;
	member.bodyItem = new BHtmlItem(body);

	root->removeChild(body);
	root->addChild(member.bodyItem);

	member.freshStyle(member.bodyItem);
	member.dirty = true;
	this->fresh();
	return true;

}
bool BHtmlWidget::write(BStream& stream) const
{
	if (!member.bodyItem)
		return false;

	BXmlNode* root = member.bodyItem->root();

	BXmlDocument document;
	return document.write(stream, root);
}

bool BHtmlWidget::read(const BString& text)
{
	BXmlDocument doc;
	BXmlNode* root = doc.read(text);
	if (!root)
		return false;

	BXmlNode* body = root->child("body");
	if (!body)
		return false;

	member.rootNode = root;
	member.bodyItem = new BHtmlItem(body);

	root->removeChild(body);
	root->addChild(member.bodyItem);

	member.freshStyle(member.bodyItem);
	member.dirty = true;
	this->fresh();
	return true;
}
bool BHtmlWidget::write(BString& text)
{
	if (!member.bodyItem)
		return false;

	BXmlNode* root = member.bodyItem->root();

	BXmlDocument document;
	return document.write(text, root);
}

void BHtmlWidget::setReadOnly(bool readOnly)
{
	if (member.readOnly != readOnly)
	{
		member.readOnly = readOnly;
		member.dirty = true;
		this->fresh();
	}
}
bool BHtmlWidget::readOnly() const
{
	return member.readOnly;
}

bool BHtmlWidget::setItemSelected(BHtmlItem* item, bool selected)
{
	if (!item)
		return false;
	if (item_member(item)->htmlWidget != this)
		return false;
	if (selected && member.inputItem != item)
	{
		member.inputItem = item;
		emit(Signal_ItemSelected, item);
		emit(Signal_SelectionChanged);
		this->fresh();
		return true;
	}
	if (!selected && member.inputItem == item)
	{
		member.inputItem = 0;
		this->fresh();
		return true;
	}
	return false;
}
BHtmlItem* BHtmlWidget::selectedItem()
{
	return member.inputItem;
}
const BHtmlItem* BHtmlWidget::selectedItem() const
{
	return member.inputItem;
}

bool BHtmlWidget::removeItem(BHtmlItem* item)
{
	if (!item)
		return false;
	if (item_member(item)->htmlWidget == this)
	{
		if (member.bodyItem == item)
		{
			member.bodyItem = 0;
			member.rootNode = 0;
			member.hoverItem = 0;
			member.inputItem = 0;
			return true;
		}
		BHtmlItem* parent = item->parent();
		if (parent)
		{
			parent->removeChild(item);
			member.hoverItem = 0;
			member.inputItem = 0;
			return true;
		}
	}
	return false;
}

BHtmlItem* BHtmlWidget::body()
{
	return member.bodyItem;
}
const BHtmlItem* BHtmlWidget::body() const
{
	return member.bodyItem;
}

BHtmlItem* BHtmlWidget::itemAt(const BPoint& point)
{
	BHtmlItem* item = member.itemAt(point, member.bodyItem);
	return item;
}
const BHtmlItem* BHtmlWidget::itemAt(const BPoint& point) const
{
	const BRect& crect = this->centerRect();
	BHtmlItem* item = member.itemAt(point, member.bodyItem);
	return item;
}

void BHtmlWidget::scrollArea()
{
	this->fresh();
	BScrollArea::scrollArea();
}

void BHtmlWidget::updateEvent(const BEvent& event)
{
	if (this->focused() && !member.readOnly)
	{
		bool inputShown = true;
		if (member.blinkTime > 0.0)
		{
			BReal now = event.now();
			BReal time = now - member.blinkTick;
			inputShown = time <= (member.blinkTime * 0.5);
			if (time > member.blinkTime)
				member.blinkTick = now;
		}
		if (member.inputShown != inputShown)
		{
			member.inputShown = inputShown;
			this->fresh();
		}
	}
}
void BHtmlWidget::resizeEvent(const BEvent& event)
{
	BScrollArea::resizeEvent(event);
	member.dirty = true;
}

void BHtmlWidget::styleEvent(const BEvent& event)
{
	BScrollArea::styleEvent(event);
	if (const BStyle* style = this->realStyle())
	{
		if (const BFont* font = this->font())
		{
			const BString& family = font->family();
			member.fontMap.def() = (BFont*)font;
			member.fontMap["h1"] = new BFont(family, 50);
			member.fontMap["h2"] = new BFont(family, 40);
			member.fontMap["h3"] = new BFont(family, 30);
			member.fontMap["h4"] = new BFont(family, 20);
			member.fontMap["h5"] = new BFont(family, 10);
			member.fontMap["h6"] = new BFont(family, 10);
			this->verticalScrollBar()->setStep(font->size());
			if (member.bodyItem)
			{
				member.freshStyle(member.bodyItem);
			}
		}
	}
	member.dirty = true;
}
void BHtmlWidget::freshEvent(const BEvent& event)
{
	if (member.dirty)
	{
		member.dirty = false;
		member.freshArea();
	}
	BScrollArea::freshEvent(event);
}
void BHtmlWidget::paintEvent(const BEvent& event)
{
	const BRect& crect = this->centerRect();

	BPainter painter(this);
	painter.drawBackground();

	if (member.bodyItem)
	{
		member.paintItem(member.bodyItem, painter);
	}

	if (!member.readOnly)
	{
		BPoint offset = this->area().pos();
		if (member.inputItem)
		{
			BRect rect = item_member(member.inputItem)->rect + offset;
			painter.setLineWidth(2);
			painter.setColor(Color_Item_Checked);
			painter.drawRect(rect);
		}
		if (member.hoverItem)
		{
			BRect rect = item_member(member.hoverItem)->rect + offset;
			painter.setColor(Color_Item_Hovered);
			painter.setLineWidth(1);
			painter.drawRect(rect);
		}
	}

	if (member.inputShown && member.inputItem)
	{
		painter.setColor(0, 0, 0);
		painter.setLineWidth(2);
		ILineBlock& block = item_member(member.inputItem)->block;
		int index = block.inputIndex;
		if (block.cells.empty())
		{
			BRect rect = block.rect;
			painter.drawLine(rect.leftTop(), rect.leftBottom());
		}
		else if (index < block.cells.size())
		{
			CodeCell& cell = block.cells[index];
			BRect rect = cell.rect + block.rect.pos();
			painter.drawLine(rect.leftTop(), rect.leftBottom());
		}
		else
		{
			CodeCell& cell = block.cells[index - 1];
			BRect rect = cell.rect + block.rect.pos();
			painter.drawLine(rect.rightTop(), rect.rightBottom());
		}
	}

	painter.drawBorder();
	painter.drawPerch();
	painter.drawMargin();
}

void BHtmlWidget::focusEvent(const BEvent& event)
{
	if (!this->focused())
	{
		member.inputShown = false;
		if (member.editing)
		{
			member.editing = false;
			this->fresh();
			emit(Signal_EditFinished);
		}
	}
	BScrollArea::focusEvent(event);
}
void BHtmlWidget::keyDownEvent(const BKeyEvent& keyEvent)
{
	if (!member.inputItem)
		return;
	if (keyEvent.keyState(Key_LCtrl))
	{
		if (keyEvent.key() == Key_V)
		{
			ILineBlock& block = item_member(member.inputItem)->block;
			block.removeSelection();
			BString text;
			bool ret = BSystem::GetClipBoardText(text);
			if (block.input(text))
			{
				member.itemTextEdited(member.inputItem);
				this->fresh();
			}
			return;
		}
		if (keyEvent.key() == Key_C)
		{
			ILineBlock& block = item_member(member.inputItem)->block;
			BString text = block.selection();
			BSystem::SetClipBoardText(text);
			return;
		}
		if (keyEvent.key() == Key_X)
		{
			ILineBlock& block = item_member(member.inputItem)->block;
			BString text = block.selection();
			BSystem::SetClipBoardText(text);
			if (block.removeSelection())
			{
				member.itemTextEdited(member.inputItem);
			}
			return;
		}
	}
	if (member.readOnly)
		return;
	switch (keyEvent.key())
	{
	case Key_Left:
	{
		ILineBlock& block = item_member(member.inputItem)->block;
		if (keyEvent.keyState(Key_LCtrl))
		{
			member.seekPrevWord();
		}
		else if (block.inputCount)
		{
			block.inputCount = 0;
		}
		else
		{
			member.seekPrev();
		}
		break;
	}
	case Key_Right:
	{
		ILineBlock& block = item_member(member.inputItem)->block;
		if (keyEvent.keyState(Key_LCtrl))
		{
			member.seekNextWord();
		}
		else if (block.inputCount)
		{
			block.inputIndex = block.inputIndex + block.inputCount;
			block.inputCount = 0;
		}
		else
		{
			member.seekNext();
		}
		break;
	}
	case Key_Up:
	{
		break;
	}
	case Key_Down:
	{
		break;
	}
	case Key_Delete:
	{
		if (member.removeSelection())
		{
			member.itemTextEdited(member.inputItem);
			this->fresh();
		}
		else if (member.removeIndex())
		{
			member.itemTextEdited(member.inputItem);
			this->fresh();
		}
		break;
	}
	case Key_Back:
	{
		if (member.removeSelection())
		{
			member.itemTextEdited(member.inputItem);
			this->fresh();
		}
		else if (member.seekPrev())
		{
			if (member.removeIndex())
			{
				member.itemTextEdited(member.inputItem);
				this->fresh();
			}
		}
		break;
	}
	default:
		return;
	}
	member.inputShown = true;
	member.blinkTick = clock() + 500;
	this->fresh();
	BScrollArea::keyDownEvent(keyEvent);
}
void BHtmlWidget::keyUpEvent(const BKeyEvent& keyEvent)
{
	BScrollArea::keyUpEvent(keyEvent);
}
void BHtmlWidget::charEvent(const BCharEvent& charEvent)
{
	if (!member.inputItem)
		return;
	if (member.readOnly)
		return;
	BCode code = charEvent.code();

	ILineBlock& block = item_member(member.inputItem)->block;
	bool edited = block.removeSelection();

	if (item_member(member.inputItem)->block.input(code))
	{
		edited = true;
		if (code == '\n')
		{
			ILineBlock& block = item_member(member.inputItem)->block;
			int line_end = block.text.find('\n');
			BString nextText = block.text.substr(line_end + 1);

			block.inputIndex -= 1;
			block.inputCount = block.cells.size() - block.inputIndex;
			block.removeSelection();

			BHtmlItem* textItem = new BHtmlItem();
			textItem->setFormat("\n<>/>\n");
			textItem->setText(nextText);
			member.inputItem->insertChild(0, textItem);

			BHtmlItem* brItem = new BHtmlItem("br");
			brItem->setFormat("/>\n");
			member.inputItem->insertChild(0, brItem);
		}
	}
	if (edited)
	{
		member.itemTextEdited(member.inputItem);
		member.inputShown = true;
		member.blinkTick = clock() + 500;
	}
	BScrollArea::charEvent(charEvent);
}

void BHtmlWidget::mouseEnterEvent(const BMouseEvent& mouseEvent)
{
	if (member.hoverItem)
	{
		member.hoverItem = 0;
		this->fresh();
	}
	this->setCursor(Cursor_Arrow);
	BScrollArea::mouseEnterEvent(mouseEvent);
}
void BHtmlWidget::mouseLeaveEvent(const BMouseEvent& mouseEvent)
{
	if (member.hoverItem)
	{
		member.hoverItem = 0;
		this->fresh();
	}
	this->setCursor(Cursor_Arrow);
	BScrollArea::mouseLeaveEvent(mouseEvent);
}

void BHtmlWidget::mousePressEvent(const BMouseEvent& mouseEvent)
{
	if (mouseEvent.button() == Button_Left)
	{
		if (member.inputItem)
		{
			item_member(member.inputItem)->block.inputCount = 0;
		}
		const BPoint& pos = mouseEvent.pos();
		BHtmlItem* item = member.itemAt(pos, member.bodyItem);
		if (member.inputItem != item)
		{
			this->setItemSelected(item, true);
		}
		if (member.inputItem && item_member(member.inputItem)->block.seekPoint(pos))
		{
			this->fresh();
		}
		member.pressed = true;
		member.pressPoint = pos;
		member.pressIndex = member.hoverIndex;
	}
	BScrollArea::mousePressEvent(mouseEvent);
}
void BHtmlWidget::mouseReleaseEvent(const BMouseEvent& mouseEvent)
{
	member.pressed = false;
	member.pressPoint.reset();
	BScrollArea::mouseReleaseEvent(mouseEvent);
}
void BHtmlWidget::mouseMoveEvent(const BMouseEvent& mouseEvent)
{
	if (member.bodyItem.empty())
		return;
	const BPoint& pos = mouseEvent.pos();
	BHtmlItem* item = member.itemAt(pos, member.bodyItem);
	if (member.hoverItem != item)
	{
		member.hoverItem = item;
		if (member.hoverItem)
		{
			if (member.hoverItem == member.inputItem)
			{
				this->setCursor(Cursor_Ibeam);
			}
			else
			{
				if (member.readOnly && member.hoverItem->name() == 'a')
					this->setCursor(Cursor_Hand);
				else
					this->setCursor(Cursor_Arrow);
			}
		}
		else
		{
			this->setCursor(Cursor_Arrow);
		}
		this->fresh();
	}
	if (member.hoverItem)
	{
		member.hoverIndex = item_member(item)->block.indexAt(pos);
	}
	if (member.pressed && member.inputItem && member.inputItem == member.hoverItem)
	{
		item_member(member.inputItem)->block.inputIndex = bMin(member.pressIndex, member.hoverIndex);
		item_member(member.inputItem)->block.inputCount = abs(member.pressIndex - member.hoverIndex);
		this->fresh();
	}
	BScrollArea::mouseMoveEvent(mouseEvent);
}

void BHtmlWidget::mouseClickEvent(const BMouseEvent& mouseEvent)
{
	const BPoint& pos = mouseEvent.pos();
	if (mouseEvent.button() == Button_Left)
	{
		if (member.hoverItem)
		{
			this->setItemSelected(member.hoverItem, true);
			this->emit(Signal_ItemClicked, member.inputItem);
			if (member.readOnly && member.hoverItem->name() == 'a')
			{
				const BString& href = member.hoverItem->attrib("href");
				if (href.beginWith('#'))
				{
					BString desc(":id=\"");
					desc.append(href, 1);
					desc.append('\"');
					BHtmlItem* item = (BHtmlItem*)member.bodyItem->find(desc);
					if (item)
					{
						BRect rect = item_member(item)->rect;
						BRect targetRect = this->centerRect().align(Align_Center, rect.size());
						int y = targetRect.top() - rect.top();
						BRect area = this->area();
						area.move(0, y);
						this->setArea(area);
						this->setItemSelected(item, true);
					}
				}
			}
		}
	}
	if (mouseEvent.button() == Button_Right)
	{
		emit(Signal_ItemRightClicked, member.inputItem);
	}
	BScrollArea::mouseClickEvent(mouseEvent);
}
void BHtmlWidget::mouseDoubleClickEvent(const BMouseEvent& mouseEvent)
{
	if (member.inputItem)
	{
		if (item_member(member.inputItem)->block.selectWord())
			this->fresh();
	}
	BScrollArea::mouseDoubleClickEvent(mouseEvent);
}

